package com.hyt.it.ogt.kq.main.controller.gov;

import static com.hyt.it.ogt.kq.common.gov.enums.CacheKeyEnum.IMPORT_CANDIDATE_PHOTO;

import java.util.*;
import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

import com.hyt.exception.annotation.ThrowsAdvice;
import com.hyt.exception.exception.BusinessLogicException;
import com.hyt.it.ogt.kq.common.gov.config.arrange.CommonLockKeyCreator;
import com.hyt.it.ogt.kq.common.gov.config.swagger.ApiVersionConstant;
import com.hyt.it.ogt.kq.common.gov.progress.ImportCandidatePhotoLockKeyCreator;
import com.hyt.it.ogt.kq.service.gov.model.entity.*;
import com.hyt.it.ogt.kq.service.gov.model.excel.ExcelErr;
import com.hyt.it.ogt.kq.service.gov.model.param.*;
import com.hyt.log.annotation.Log;
import com.hyt.progress.creator.anno.ProgressAnno;
import com.hyt.swagger.ApiVersion;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hyt.common.apiInfor.ApiResponse;
import com.hyt.common.apiInfor.DataType;
import com.hyt.common.apiInfor.ParamType;
import com.hyt.core.util.FastJsonUtil;
import com.hyt.core.util.UUIDUtils;
import com.hyt.it.ogt.kq.common.config.ConfigManager;
import com.hyt.it.ogt.kq.common.config.exception.KqException;
import com.hyt.it.ogt.kq.common.exception.DataBeingLockedException;
import com.hyt.it.ogt.kq.common.gov.constant.ResponseCode;
import com.hyt.it.ogt.kq.common.gov.constant.StringCodeEnum;
import com.hyt.it.ogt.kq.common.gov.utils.TextUtil;
import com.hyt.it.ogt.kq.main.controller.base.BaseController;
import com.hyt.it.ogt.kq.service.gov.model.vo.BucketVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.CandidateBaseInfoVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.CandidateExtVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.CandidatePageVO;
import com.hyt.it.ogt.kq.service.gov.photo.AbstractPhotoTemplate;
import com.hyt.it.ogt.kq.service.gov.photo.PhotoFactory;
import com.hyt.it.ogt.kq.service.gov.photo.PhotoTypeEnum;
import com.hyt.it.ogt.kq.service.gov.service.GovIDictService;
import com.hyt.it.ogt.kq.service.gov.service.ICandidateService;
import com.hyt.it.ogt.kq.service.gov.service.ISubjectService;
import com.hyt.it.ogt.kq.service.gov.service.ITaskAreaService;
import com.hyt.it.ogt.kq.service.gov.service.ITaskCandidateAreaService;
import com.hyt.it.ogt.kq.service.gov.service.ITaskPlaceService;
import com.hyt.it.ogt.kq.service.gov.service.ITaskRoomService;
import com.hyt.it.ogt.kq.service.gov.service.ITaskService;
import com.hyt.it.ogt.kq.service.gov.service.ITimeRoomCandidateService;
import com.hyt.it.ogt.kq.service.model.entity.Candidate;
import com.hyt.it.ogt.kq.service.model.entity.Subject;
import com.hyt.it.ogt.kq.service.model.entity.TaskCandidateArea;
import com.hyt.loginfo.model.User;

import cn.hutool.core.collection.CollUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiOperationSort;
import io.swagger.annotations.ApiSort;
import lombok.extern.slf4j.Slf4j;

/**
 * <p>
 * 考生表 前端控制器
 * </p>
 *
 * @author huangyh
 * @since 2021-03-08
 */
@RestController
@Api(tags = "105.考生接口", value = "考生接口")
@ApiSort(value = 105)
@RequestMapping("/kw/gov/candidate")
@Slf4j
public class CandidateController extends BaseController {
	
	@Resource
	private ConfigManager configManager;

    @Resource
    private PhotoFactory photoFactory;

    @Resource
    private ICandidateService iCandidateService;

    @Resource
    private GovIDictService iDictService;

    @Resource
    private ITaskCandidateAreaService iTaskCandidateAreaService;

    @Resource
    private ITaskService iTaskService;
    
    @Resource
    private ITaskAreaService taskAreaService;

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Resource
    private ITimeRoomCandidateService iTimeRoomCandidateService;
    
    @Resource
    private ITaskRoomService taskRoomService;
    
    @Resource
    private ISubjectService taskSubjectService;
    
    @Resource
    private ITaskPlaceService taskPlaceService;
    
    @ApiOperation(
            value = "5.1 上传考生照片",
            notes = "30000:上传考生照片成功;" +
                    "30828:图片类型不存在;" +
                    "30861:图片文件为空;" +
                    "30850:不是一个有效地图片文件;" +
                    "30851:图片文件后缀名不正确;" +
                    "30852:图片文件大小不在规定范围内;" +
                    "30853:图片宽度不在规定范围内;" +
                    "30854:图片高度不在规定范围内;"+
                    "30855:上传考生照片异常;"
    )
    @ApiOperationSort(value = 1)
    @RequestMapping(value = "/uploadPhoto", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ApiResponse<Object> uploadPhoto(@RequestPart(value = "file")MultipartFile file, Integer type,String taskId) {
        try {
            // 校验任务数据锁定状态(后续版本考虑优化为从缓存中获取状态)
            iTaskService.isLockDataThrowException(taskId);
            // 前端没传type时，默认为上传考生个人照，后续可支持多种照片类型
            taskId = TextUtil.isNull(taskId)?UUIDUtils.newSortUUID():taskId;
            type = type == null ? PhotoTypeEnum.PERSONAL.getCode() : type;
            AbstractPhotoTemplate photoTemplate = photoFactory.getPhoto(type);
            CandidateFileParam candidateFileParam = photoTemplate.upload(file,taskId);
            return ApiResponse.builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .data(candidateFileParam)
                    .build();
        } catch (KqException k) {
            log.error("# 上传考生照片业务异常: {}", k.getErrMsg());
            return ApiResponse.builder()
                    .code(k.getErrCode())
                    .data(k.getMessage())
                    .build();
        } catch (Exception e) {
            log.error("# 上传考生照片异常:", e);
            return ApiResponse.builder()
                    .code(ResponseCode.ERROR_CANDIDATE_UPLOAD_PHOTO_EXCEPTION.getCode())
                    .build();
        }
    }

    @ApiOperation(
            value = "5.2 添加考生",
            notes = "30000:添加考生成功;" +
                    "30845:添加考生参数校验错误;" +
                    "30846:添加考生异常;" +
                    "30847:插入考生照片失败; " +
                    "30848:插入考生表失败;" +
                    "30863:考生身份证号码已存在;" +
                    "30849:插入考生科目中间表失败;"
    )
    @ApiOperationSort(value = 2)
    @PostMapping("/add")
    public ApiResponse<Object> add(@RequestBody @Valid CandidateParam candidateParam) {
        log.info("# 添加考生参数 ： {}", candidateParam.toString());
        User user = getUser();
        try {
            iCandidateService.add(candidateParam,user);
            return ApiResponse.builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .build();
        } catch (KqException k) {
            log.error("# 添加考生业务异常: {}", k.getErrMsg());
            return ApiResponse.builder()
                    .code(k.getErrCode())
                    .data(k.getMessage())
                    .build();
        } catch (Exception e) {
            log.error("# 添加考生异常: ", e);
            return ApiResponse.builder()
                    .code(ResponseCode.ERROR_ADD_CANDIDATE_EXCEPTION.getCode())
                    .build();
        }
    }

    @ApiOperation(
            value = "5.3 查询考生信息分页数据",
            notes = "20000:成功;" +
                    "30843:考试任务id为空; " +
                    "30844:分页获取考生异常;"
    )
    @ApiOperationSort(value = 3)
    @RequestMapping(value = "/page", method = RequestMethod.POST)
    public ApiResponse<Page<CandidatePageVO>> page(@RequestBody CandidatePageParam param) {
        log.info("# 查询考生信息分页数据参数: {}", JSONObject.toJSON(param));
        try {
            if (StringUtils.isEmpty(param.getTaskId())) {
                //考试任务id不能为空
                return ApiResponse.<Page<CandidatePageVO>>builder()
                        .code(ResponseCode.ERROR_PAGE_FIND_CANDIDATE_TASK_ID_IS_NULL.getCode())
                        .build();
            }
            Page<CandidatePageVO> page = iCandidateService.pageOfCandidate(param);
            return ApiResponse.<Page<CandidatePageVO>>builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .data(page)
                    .build();
        } catch (KqException k) {
            log.error("# 批量导入个人照片业务异常: {}", k.getErrMsg());
            return ApiResponse.<Page<CandidatePageVO>>builder()
                    .code(k.getErrCode())
                    .build();
        } catch (Exception e) {
            log.error("# 查询考生信息分页数据异常:", e);
            return ApiResponse.<Page<CandidatePageVO>>builder()
                    .code(ResponseCode.ERROR_PAGE_FIND_CANDIDATE_EXCEPTION.getCode())
                    .build();
        }
    }

    public static void main(String[] args) {
        String str = "{\"current\":1,\"size\":10,\"searchText\":\"\",\"subjectId\":\"\",\"cityId\":\"\",\"districtId\":\"\",\"provinceId\":\"\",\"deptId\":\"\",\"taskId\":\"ZZZZZZZZZZA151896940232052736251\"}";
        CandidatePageParam candidatePageParam = JSON.parseObject(str, CandidatePageParam.class);
        System.out.println(candidatePageParam);
    }

    @ApiOperation(
            value = "5.4 查询考生详细信息",
            notes = "20000:成功; " +
                    "30801:考生id不能为空;" +
                    "30841:查询考生不存在;" +
                    "30842:查询考生异常;",
            response = CandidateParam.class
    )
    @ApiOperationSort(value = 4)
    @RequestMapping(value = "/query", method = RequestMethod.GET)
    public ApiResponse<CandidateBaseInfoVO> query(String candidateId) {
        log.info("# 查询考生详细信息参数 ： {}", candidateId);
        try {
            if (StringUtils.isEmpty(candidateId)) {
                //考生id不能为空
                return ApiResponse.<CandidateBaseInfoVO>builder()
                        .code(ResponseCode.ERROR_CANDIDATE_ID_CANNOT_BE_EMPTY.getCode())
                        .build();
            }
            CandidateBaseInfoVO baseInfoVO = iCandidateService.query(candidateId);
            if (ObjectUtils.isEmpty(baseInfoVO)) {
                //考生不存在
                return ApiResponse.<CandidateBaseInfoVO>builder()
                        .code(ResponseCode.ERROR_FIND_CANDIDATE_NON_EXISTENT.getCode())
                        .build();
            }
            return ApiResponse.<CandidateBaseInfoVO>builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .data(baseInfoVO)
                    .build();
        } catch (Exception e) {
            log.error("# 查询考生详细信息异常:", e);
            return ApiResponse.<CandidateBaseInfoVO>builder()
                    .code(ResponseCode.ERROR_FIND_CANDIDATE_EXCEPTION.getCode())
                    .build();
        }
    }

    @ApiOperation(
            value = "5.5 批量导入照片",
            notes = "30000:成功;" +
                    "30829:批量导入考生照片异常;"+
                    "30830:考试任务id为空;" +
                    "30831:插入考生照片失败;" +
                    "30832:导入文件不存在;" +
                    "30833:导入文件后缀名不正确;" +
                    "30834:不是一个有效地zip文件;" +
                    "30835:压缩包中包含不是图片的文件;" +
                    "30836:压缩包中文件后缀名不是正确的图片后缀名;" +
                    "30837:压缩包中图片高度不在规定范围内;" +
                    "30838:压缩包中图片宽度不在规定返回内;" +
                    "30839:压缩包中图片大小不在规定范围内;" +
                    "30840:压缩包中含有不是当前考试任务中的考生;" +
                    "30860:压缩包中含有已上传的考生照片;"
    )
    @ApiOperationSort(value = 5)
    @RequestMapping(value = "/importPersonalPhoto", method = RequestMethod.POST)
    @ProgressAnno(total = 100, timeoutSeconds = 60 * 10, errorCode = 43050502, canConcurrent = false, lockKeyCreator = ImportCandidatePhotoLockKeyCreator.class)
    public void importPersonalPhoto(MultipartFile file, Integer type, String taskId) {
        log.info("# 批量导入个人照片参数: {}, {}", type, taskId);
        if (StringUtils.isEmpty(taskId)) {
            //考试任务id不能为空
            BusinessLogicException.throwException(ResponseCode.COMMON_PARAM_INVALID.getCode(), "任务id不能为空");
        }
        //是否已锁定数据
        Task task = iTaskService.lambdaQuery().eq(Task::getId, taskId).last("LIMIT 1").one();
        if(Objects.isNull(task) || task.getDataLock()){
            BusinessLogicException.throwException(ResponseCode.TASK_DATA_LOCK_TRUE.getCode(), "数据已锁定，不允许操作此功能。");
        }
        // 校验任务数据锁定状态(后续版本考虑优化为从缓存中获取状态)
        AbstractPhotoTemplate photoTemplate = photoFactory.getPhoto(type);
        if (ObjectUtils.isEmpty(photoTemplate)) {
            //照片类型不存在
            BusinessLogicException.throwException(ResponseCode.COMMON_PARAM_INVALID.getCode(), "参数照片类型不存在。");
        }
        photoTemplate.importPhoto(file, taskId);
    }

    @ApiOperation(
            value = "5.6 修改考生",
            notes = "30000:修改考生成功; " +
                    "30821:修改考生参数校验错误;" +
                    "30822:修改考生异常;" +
                    "30823:删除考生照片异常;" +
                    "30858:考生照片不存在;" +
                    "30859:更新考生照片失败;" +
                    "30824:更新考生失败;" +
                    "30825:更新的考生不存在;" +
                    "30826:删除考生科目失败;" +
                    "30827:插入考生科目失败;"+
                    "30856:当前任务下暂无科目数据;"+
                    "30863:考生身份证号码已存在;"+
                    "30857:考生报考的科目中有已不存在的科目;"
    )
    @ApiOperationSort(value = 6)
    @RequestMapping(value = "/update", method = RequestMethod.POST)
    public ApiResponse<Object> update(@RequestBody @Valid CandidateParam candidateParam) {
        log.info("# 修改考生参数：{}", candidateParam.toString());
        try {
            iCandidateService.update(candidateParam);
            return ApiResponse.builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .build();
        } catch (KqException k) {
            log.error("# 修改考生业务异常: {}", k.getErrMsg());
            return ApiResponse.builder()
                    .code(k.getErrCode())
                    .data(k.getMessage())
                    .build();
        } catch (DataBeingLockedException d) {
            return ApiResponse.builder()
                    .code(d.getCode())
                    .data(d.getMessage())
                    .build();
        } catch (Exception e) {
            log.error("# 修改考生异常 :", e);
            return ApiResponse.builder()
                    .code(ResponseCode.ERROR_UPDATE_CANDIDATE_EXCEPTION.getCode())
                    .build();
        }
    }

    @ApiOperation(
            value = "5.7 获取考生性别类型",
            notes = "30000:获取考生性别类型;" +
                    "30820:获取考生性别类型异常"
    )
    @ApiOperationSort(value = 7)
    @GetMapping( "/getSexType")
    public ApiResponse<List<Dict> > getSexType() {

        try {

            return ApiResponse.<List<Dict>>builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .data(iDictService.getSexType())
                    .build();
        } catch (Exception e) {
            log.error("# 获取考生性别类型异常:", e);
            return ApiResponse.<List<Dict>>builder()
                    .code(ResponseCode.ERROR_GET_CANDIDATE_SEX_TYPE_EXCEPTION.getCode())
                    .build();
        }
    }

    @ApiOperation(
            value = "5.8 获取考生证件类型",
            notes = "30000:获取考生证件类型;" +
                    "30819:获取考生证件类型异常"
    )
    @ApiOperationSort(value = 8)
    @GetMapping( "/getCardType")
    public ApiResponse<List<Dict>> getCardType() {

        try {
            return ApiResponse.<List<Dict>>builder()
                .code(ResponseCode.SUCCESS.getCode())
                .data(iDictService.getCardType())
                .build();
        } catch (Exception e) {
            log.error("# 获取考生证件类型异常:", e);
            return ApiResponse.<List<Dict>>builder()
                    .code(ResponseCode.ERROR_GET_CANDIDATE_ID_TYPE_EXCEPTION.getCode())
                    .build();
        }
    }

    @ApiOperation(
            value = "5.9 获取考生考生区域(省)",
            notes = "30000:获取考生考生区域(省);" +
                    "30818:区域信息异常"
    )
    @ApiOperationSort(value = 9)
    @GetMapping( "/getCandidateAreaProvince")
    public ApiResponse<List<TaskCandidateArea>> getCandidateAreaProvince(CandidateAreaQueryParam candidateAreaQueryParam) {

        try {

            return ApiResponse.<List<TaskCandidateArea>>builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .data(iTaskCandidateAreaService.getCandidateAreaProvince(candidateAreaQueryParam.getTaskId()))
                    .build();
        } catch (Exception e) {
            log.error("# 获取考生考生区域(省)异常:", e);
            return ApiResponse.<List<TaskCandidateArea>>builder()
                    .code(ResponseCode.ERROR_GET_AREA_PROVINCE_EXCEPTION.getCode())
                    .build();
        }
    }

    @ApiOperation(
            value = "5.10 获取考生考生区域(市)",
            notes = "30000:获取考生考生区域(市)" +
                    "30818:区域信息异常"
    )
    @ApiOperationSort(value = 10)
    @GetMapping( "/getCandidateAreaCity")
    public ApiResponse<List<TaskCandidateArea>> getCandidateAreaCity(CandidateAreaQueryParam candidateAreaQueryParam) {

        try {

            return ApiResponse.<List<TaskCandidateArea>>builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .data(iTaskCandidateAreaService.getCandidateAreaCity(candidateAreaQueryParam.getTaskId(), candidateAreaQueryParam.getProvinceId()))
                    .build();
        } catch (Exception e) {
            log.error("# 获取考生考生区域(市)异常:", e);
            return ApiResponse.<List<TaskCandidateArea>>builder()
                    .code(ResponseCode.ERROR_GET_AREA_CITY_EXCEPTION.getCode())
                    .build();
        }
    }

    @ApiOperation(
            value = "5.11 获取考生考生区域(县区)",
            notes = "30000:获取考生考生区域(县区);" +
                    "30818:区域信息异常"
    )
    @ApiOperationSort(value = 11)
    @GetMapping( "/getCandidateAreaDistrict")
    public ApiResponse<List<TaskCandidateArea>> getCandidateAreaDistrict(CandidateAreaQueryParam candidateAreaQueryParam) {

        try {

            return ApiResponse.<List<TaskCandidateArea>>builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .data(iTaskCandidateAreaService.getCandidateAreaDistrict(candidateAreaQueryParam.getTaskId(), candidateAreaQueryParam.getCityId()))
                    .build();
        } catch (Exception e) {
            log.error("# 获取考生考生区域(县区)异常:", e);
            return ApiResponse.<List<TaskCandidateArea>>builder()
                    .code(ResponseCode.ERROR_GET_AREA_DISTRICT_EXCEPTION.getCode())
                    .build();
        }
    }

    @ApiOperation(
            value = "5.12 删除考生数据",
            notes = "30000:删除考生数据成功," +
                    "30801:入参考生ids为空，" +
                    "30802:删除失败"
    )
    @ApiOperationSort(value = 12)
    @DeleteMapping("/deleteCandidate")
    public ApiResponse<Object> deleteCandidate(@RequestParam String taskId,
                                               @RequestParam(required = false) String candidateIds) {
        try {
            iCandidateService.delete(taskId, candidateIds);
            return ApiResponse.builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .build();
        } catch (KqException k) {
            log.error("删除考生数据业务异常: {}", k.getErrMsg());
            return ApiResponse.builder()
                    .code(k.getErrCode())
                    .data(k.getMessage())
                    .build();
        } catch (Exception e) {
            log.error("# 删除考生数据异常:", e);
            return ApiResponse.builder()
                    .code(ResponseCode.ERROR_FAILED_TO_DELETE_CANDIDATE.getCode())
                    .build();
        }
    }

    @ApiOperation(
            value = "5.13 导入考生信息模板下载",
            notes = "30406:taskId不能为空;" +
                    "30803:导入考生模板生成失败;"
    )
    @ApiOperationSort(value = 13)
    @ThrowsAdvice(code = 43051301, print = "生成导入考生模板失败")
    @GetMapping("/exportCandidateTemplate")
    public void exportCandidateTemplate(HttpServletResponse response, String taskId) {
        iCandidateService.exportCandidateTemplate(response, taskId,getUser());
    }

    @ApiOperation(
            value = "5.14 考生批量导入",
            notes = "30804:文件不能为空," +
                    "30805:不是有效地 .xlsx文件," +
                    "30806:文件体积错误," +
                    "30807:读取excel文件失败," +
                    "30808:表格中读取到了不完整行," +
                    "30809:表中存在考生性别不符合约束," +
                    "30810:导入数据中存在与证件类型约束不符合项," +
                    "30811:导入数据中存在与作答类型约束不符合项," +
                    "30812:导入数据中存在存在与语种类型约束不符合项," +
                    "30813:导入考生中,同一个考生存在相同科目;（data中有返回[idNumber..]）," +
                    "30814:存在已录入的考生证件号（data中有返回已存在的[idNumber..]）," +
                    "30815:科目id不存在（data中有返回不存在的[subjectId..]）" +
                    "30816:批量导入失败;" +
                    "30817:考试任务id为空;" +
                    "30862:导入考生数据中存在生日日期格式不正确"
    )
    @ApiOperationSort(value = 14)
    @ApiVersion(group = {ApiVersionConstant.V2_4_10})
    @Log(module = "考务考生管理", business = "考生批量导入")
    @ProgressAnno(total = 100, timeoutSeconds = 60 * 20, errorCode = 43051401, canConcurrent = false, lockKeyCreator = CommonLockKeyCreator.class)
    @GetMapping("/batchImportCandidate")
    public void importCandidates(@RequestParam String fileUrl, String taskId) {
        User user = getUser();
        if (Objects.isNull(user) || Objects.isNull(user.getId())) {
            BusinessLogicException.throwException(ResponseCode.USER_NOT_LOGIN.getCode(), ResponseCode.USER_NOT_LOGIN.getMsg(), fileUrl);
        }
        iCandidateService.batchImportCandidate(fileUrl, taskId, user);
    }

    /**
     * 获取云存储Bucket信息
     * @param taskId 考试任务id
     * @return 云存储Bucket信息
     */
    @ApiOperation(
            value = "5.15 获取云存储Bucket信息",
            notes = "30817:获取云存储Bucket信息失败; 30818:考试任务不存在"
    )
    @ApiOperationSort(value = 15)
    @ApiImplicitParams({
            @ApiImplicitParam(name = "taskId", value = "任务id", dataType = DataType.STRING, paramType = ParamType.QUERY, required = true)
    })
    @GetMapping("/getBucketInfo")
    public ApiResponse<BucketVO> getBucketInfo(String taskId) {
        try {
            String officeId = getOfficeId();
            BucketVO bucketVO = iCandidateService.getBucketInfo(taskId, officeId);
            return ApiResponse.<BucketVO>builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .data(bucketVO)
                    .build();
        } catch (KqException k) {
            log.error("# 获取云存储Bucket信息业务异常: {}", k.getErrMsg());
            return ApiResponse.<BucketVO>builder()
                    .code(k.getErrCode())
                    .build();
        } catch (Exception e) {
            log.error("# 获取云存储Bucket信息异常:", e);
            return ApiResponse.<BucketVO>builder()
                    .code(ResponseCode.FAIL_TO_GET_BUCKET_INFO.getCode())
                    .build();
        }
    }

    @ApiOperation(
            value = "5.16 推送单个考生编排数据到考试"
    )
    @ApiOperationSort(value = 16)
    @GetMapping("/pusSimpleCandidateToKs")
    public ApiResponse<BucketVO> pusSimpleCandidateToKs(@RequestParam String type, @RequestParam String taskId,
                                                        @RequestParam(required = false) String candidateId) {
        try {
            if(!StringCodeEnum.PUSH_TYPE_DELETE.getCode().equals(type) && !StringCodeEnum.PUSH_TYPE_INSERT.getCode().equals(type)) {
                return ApiResponse.<BucketVO>builder().code(ResponseCode.PUSH_SIMPLE_CANDIDATE_SUPPORT_TYPE.getCode()).build();
            }
            iTimeRoomCandidateService.pushSimpleCandidateToKs(taskId, candidateId, type);
            return ApiResponse.<BucketVO>builder().code(ResponseCode.SUCCESS.getCode()).build();
        } catch (Exception e) {
            log.error("# 推送单个考生编排数据到考试异常:", e);
            return ApiResponse.<BucketVO>builder().code(ResponseCode.PUSH_SIMPLE_CANDIDATE_ERROR.getCode()).build();
        }
    }

    
    @ApiOperation(
            value = "5.17 根据考试id，准考证号获取考生区域和编排信息", notes = "20000：成功；"
            		+ "43051701：登录用户信息不存在, "
            		+ "43051702：获取不到考生的编排数据,"
            		+ "43051703：获取不到任务考场信息,"
            		+ "43051704: 获取不到任务考点信息,"
            		+ "43051705: 获取不到任务考生信息, "
    )
    @ApiImplicitParams({
    	@ApiImplicitParam(
    		name = "taskId", value = "考试任务id", dataType = "string", paramType = "query", required = true
    	),
		@ApiImplicitParam(
			name = "examId", value = "考试id", dataType = "string", paramType = "query", required = true
		),
		@ApiImplicitParam(
			name = "admissionNum", value = "考生准考证号", dataType = "string", paramType = "query", required = true
		)
	})
    @ApiOperationSort(value = 17)
    @GetMapping("/getCandidateAreaInfoAndArrangeInfo")
    public ApiResponse<CandidateExtVO> getCandidateAreaInfoAndArrangeInfo(
    		@RequestParam(required = true) String taskId, 
    		@RequestParam(required = true) String examId, 
    		@RequestParam(required = true) String admissionNum) {
        log.info("根据考试id，准考证号获取考生区域和编排信息查询参数 {} {} {}", taskId, examId, admissionNum);
    	CandidateExtVO candidateExtVO = new CandidateExtVO();
    	User loginUser = getUser();
    	if(null == loginUser || StringUtils.isEmpty(loginUser.getId())) {
    		return ApiResponse.<CandidateExtVO>builder().code(43051701).build();
    	}
    	try {
    		// 首先获取考生的编排信息
    		TimeRoomCandidate timeRoomCandidate = null;
    		String timeRoomCandidateCatchValue = redisTemplate.opsForValue().get(TimeRoomCandidate.getTimeRoomCandidateCacheKey(taskId, examId, admissionNum));;
    		if (StringUtils.isNotEmpty(timeRoomCandidateCatchValue)) {
    			timeRoomCandidate = FastJsonUtil.getJsonToBean(timeRoomCandidateCatchValue, TimeRoomCandidate.class);
    		} else {
    			timeRoomCandidate = iTimeRoomCandidateService.geTimeRoomCandidateByTaskidAndAdnumber(taskId, examId, admissionNum);
    			if (null != timeRoomCandidate) {
    				String timeRoomCandidateJson = JSON.toJSONString(timeRoomCandidate);
    				redisTemplate.opsForValue().set(TimeRoomCandidate.getTimeRoomCandidateCacheKey(taskId, examId, admissionNum), timeRoomCandidateJson, configManager.getRedisDataInvalidTime(), TimeUnit.SECONDS);
    			}
    		}
    		if (null == timeRoomCandidate) {
    			return ApiResponse.<CandidateExtVO> builder().code(43051702).build();
    		} 

        	candidateExtVO.setSeatNum(timeRoomCandidate.getSeatNum());
        	if(StringUtils.isNotEmpty(timeRoomCandidate.getSubjectId())) {
        		candidateExtVO.setSubjectId(timeRoomCandidate.getSubjectId());
        		Subject subject = taskSubjectService.getById(timeRoomCandidate.getSubjectId());
        		if(null != subject) {
        			candidateExtVO.setSubjectDesc(subject.getName());
        		}
        	}
        	TaskRoom taskRoom = null;
        	if(!StringUtils.isEmpty(timeRoomCandidate.getRoomId())) {
        		// 获取考生的考点信息
        		taskRoom = taskRoomService.getById(timeRoomCandidate.getRoomId());
        	}
        	if(null == taskRoom) {
        		return ApiResponse.<CandidateExtVO>builder().code(43051703).build();
        	}
        	candidateExtVO.setRoomId(timeRoomCandidate.getRoomId());
        	candidateExtVO.setRoomDesc(taskRoom.getName());
        	// 获取考点信息
        	TaskPlace taskPlace = null;
        	if(!StringUtils.isEmpty(taskRoom.getPlaceId())) {
        		taskPlace = taskPlaceService.getById(taskRoom.getPlaceId());
        	}
        	if(null == taskPlace) {
        		return ApiResponse.<CandidateExtVO>builder().code(43051704).build();
        	}
        	candidateExtVO.setPlaceId(taskRoom.getPlaceId());
        	candidateExtVO.setPlaceDesc(taskPlace.getName());

        	// 获取省市区
        	Candidate candidate = null;
        	if(StringUtils.isNotEmpty(timeRoomCandidate.getCandidateId())) {
        		candidate = iCandidateService.getById(timeRoomCandidate.getCandidateId());
        	}
        	if(null == candidate) {
        		return ApiResponse.<CandidateExtVO>builder().code(43051705).build();
        	}
        	
        	List<String> areaIds = new ArrayList<>();
        	if(StringUtils.isNotEmpty(candidate.getProvince())) {
        		areaIds.add(candidate.getProvince());
        	}
        	if(StringUtils.isNotEmpty(candidate.getCity())) {
        		areaIds.add(candidate.getCity());
        	}
        	if(StringUtils.isNotEmpty(candidate.getDistrict())) {
        		areaIds.add(candidate.getDistrict());
        	}
    		Map<String, String> taskAreasMaps = new HashMap<>();
    		List<String> queryDBAreaIds = new ArrayList<>();
    		if (CollUtil.isNotEmpty(areaIds)) {
    			for (String areaId : areaIds) {
    				TaskCandidateArea taskCandidateArea = null;
    				String taskCandidateAreaCacheValue = redisTemplate.opsForValue().get(TaskCandidateArea.getTaskCandidateAreaCacheKey(taskId, areaId));
    				if (StringUtils.isNotEmpty(taskCandidateAreaCacheValue)) {
    					taskCandidateArea = FastJsonUtil.getJsonToBean(taskCandidateAreaCacheValue, TaskCandidateArea.class);
    					if(StringUtils.isNotEmpty(taskCandidateArea.getAreaId()) && StringUtils.isNotEmpty(taskCandidateArea.getName())) {
    						taskAreasMaps.put(taskCandidateArea.getAreaId(), taskCandidateArea.getName());
    					}
    				} else {
    					queryDBAreaIds.add(areaId);
    				}
    			}
    			if (CollUtil.isNotEmpty(queryDBAreaIds)) {
    				List<TaskCandidateArea> taskAreas = iTaskCandidateAreaService.listByTaskIdAndAreaIds(taskId, queryDBAreaIds);
    				if (CollUtil.isNotEmpty(taskAreas)) {
    					for (TaskCandidateArea taskArea : taskAreas) {
    						String taskAreaJson = JSON.toJSONString(taskArea);
    						redisTemplate.opsForValue().set(TaskCandidateArea.getTaskCandidateAreaCacheKey(taskId, taskArea.getAreaId()), taskAreaJson, configManager.getRedisDataInvalidTime(), TimeUnit.SECONDS);
    						taskAreasMaps.put(taskArea.getAreaId(), taskArea.getName());
    					}
    				}
    			}
    		}
    		candidateExtVO.setProvince(taskAreasMaps.get(candidate.getProvince()));
    		candidateExtVO.setCity(taskAreasMaps.get(candidate.getCity()));
    		candidateExtVO.setDistrict(taskAreasMaps.get(candidate.getDistrict()));
    		return ApiResponse.<CandidateExtVO> builder().code(ResponseCode.SUCCESS.getCode()).data(candidateExtVO).build();
		} catch (Exception e) {
			log.error("getCandidateAreaInfoAndArrangeInfo error", e);
		}
		
    	return ApiResponse.<CandidateExtVO> builder().code(ResponseCode.SUCCESS.getCode()).data(candidateExtVO).build();
    }


    @ApiOperation(
            value = "5.18 从平台导入机构内部考生", notes = "20000: 成功 43051421 任务id不能为空"
    )
    @ApiOperationSort(value = 18)
    @PostMapping("/batchImportFromPt")
    public ApiResponse<Object> batchImportFromPt(@RequestBody PtCandidateImportParam param) {
        log.info("从平台导入机构内部考生，入参：{}",param);
        try {
            if(Objects.isNull(param.getTaskId())){
                return ApiResponse.builder()
                        .code(ResponseCode.ERROR_EXPORT_CANDIDATE_TEMPLATE_TASK_ID_NULL.getCode())
                        .build();
            }
            iCandidateService.batchImportFromPt(param);
            return ApiResponse.builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .build();
        } catch (KqException k) {
            log.error("# 批量导入考生业务异常:", k);
            return ApiResponse.builder()
                    .code(k.getErrCode())
                    .data(k.getErrMsg())
                    .build();
        } catch (ExcelAnalysisException e) {
            log.error("# 批量导入考生数据不合法: {}", e.getMessage());
            return ApiResponse.builder()
                    .code(ResponseCode.DATA_VALIDATION_ERROR.getCode())
                    .data("<div style=\"white-space:normal; word-break:break-all;\">" + e.getMessage() + "</div>")
                    .build();
        } catch (Exception e) {
            log.error("# 批量导入考生异常:", e);
            return ApiResponse.builder()
                    .code(ResponseCode.ERROR_BATCH_IMPORT_FROM_PT.getCode())
                    .build();
        }
    }

    @ApiOperation(
            value = "5.19 查询考试任务选择的考生", notes = "20000: 成功 43051901 查询考试任务选择的考生失败"
    )
    @ApiOperationSort(value = 19)
    @GetMapping("/queryTaskCandiate")
    public ApiResponse<Object> queryTaskCandiate(@RequestParam String taskId) {
        log.info("查询考试任务选择的考生，入参：{}",taskId);
        try {
            List<String> candiateIds = iCandidateService.queryTaskCandiate(taskId);
            return ApiResponse.builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .data(candiateIds)
                    .build();
        } catch (KqException k) {
            log.error("# 查询考试任务选择的考生异常:", k);
            return ApiResponse.builder()
                    .code(k.getErrCode())
                    .data(k.getErrMsg())
                    .build();
        } catch (Exception e) {
            log.error("# 查询考试任务选择的考生异常:", e);
            return ApiResponse.builder()
                    .code(ResponseCode.QUERY_TASK_CANDIDATE_ERROR.getCode())
                    .build();
        }
    }

    @ApiOperation(
            value = "5.20 考生信息导出"
    )
    @ApiOperationSort(value = 20)
    @ApiVersion(group = ApiVersionConstant.V2_4_10)
    @ProgressAnno(total = 100, timeoutSeconds = 60 * 10, errorCode = 43052001)
    @PostMapping(value = "/exportCandidateExcel")
    public void exportCandidateExcel(@RequestParam String taskId) {
        iCandidateService.exportExcel(taskId);
    }
}
